﻿/*
 * Clase adaptada para que pueda generar un FLV a partir de un array de frames, la clase original es:
 * 
	SimpleFlvWriter.as
	Lee Felarca
	http://www.zeropointnine.com/blog
	5-2007
	v0.8
	Source code licensed under a Creative Commons Attribution 3.0 License.
	http://creativecommons.org/licenses/by/3.0/
	Some Rights Reserved.
*/
	
package igz.fleaxy.video
{
    import flash.display.BitmapData;
    import flash.utils.ByteArray;

	public class FlvManager {
		private const _BLOCK_WIDTH  :int = 32;
		private const _BLOCK_HEIGHT :int = 32;
		
		private var _FrameWidth:int;
		private var _FrameHeight:int;
		private var _FrameRate:Number;
		private var _Duration:Number;

		private var _CurrentTagSize:uint = 0;
		private var _FlvTagVideoC:int = 0;
		private var _Bmp:BitmapData;

		public function FlvManager() {
		}
		
		/**
		 * Convierte un array de bitmapdata con los frames del video a formato FLV
		 * 
		 * @param	p_bitmapData Array con los frames del video en bitmapdata
		 * @param	p_width Ancho
		 * @param	p_height Alto
		 * @param	p_fps Frames por segundo
		 * @param	p_duration Duración en segundos
		 * 
		 * @return Devuelve un ByteArray con los datos del video en formato FLV
		 */
		public function $ToFlv ( p_bitmapData:Array, p_width:uint, p_height:uint, p_fps:Number, p_duration:Number = 0 ) : ByteArray {
			_FrameWidth = p_width;
			_FrameHeight = p_height;
			_FrameRate = p_fps;
			_Duration = p_duration;
			
			var flvByteArray : ByteArray = new ByteArray();
			flvByteArray.writeBytes( _GetHeader() );
			flvByteArray.writeUnsignedInt( _CurrentTagSize );
			flvByteArray.writeBytes( _GetFlvTagOnMetaData() );
			
			var frame : BitmapData;
			for each ( frame in p_bitmapData ) {
				_Bmp = frame;
				flvByteArray.writeUnsignedInt( _CurrentTagSize );
				flvByteArray.writeBytes( _GetFlvTagVideo() );	
			}
			return flvByteArray;
		}

		private function _GetHeader() : ByteArray {
			var ba:ByteArray = new ByteArray();
			ba.writeByte(0x46) // 'F'
			ba.writeByte(0x4C) // 'L'
			ba.writeByte(0x56) // 'V'
			ba.writeByte(0x01) // Version 1
			ba.writeByte(0x01) // misc flags - video stream only
			ba.writeUnsignedInt(0x09) // header length
			return ba;
		}

		private function _GetFlvTagVideo():ByteArray {
			var tag:ByteArray = new ByteArray();
			var dat:ByteArray = _GetVideoData();
			var timeStamp:uint = uint(1000/_FrameRate * _FlvTagVideoC++);

			// tag 'header'
			tag.writeByte( 0x09 ); 					// tagType = video
			_WriteUI24(tag, dat.length); 			// data size
			_WriteUI24(tag, timeStamp);				// timestamp in ms
			tag.writeByte(0);						// timestamp extended, not using *** 
			_WriteUI24(tag, 0);						// streamID always 0
			
			// videodata			
			tag.writeBytes( dat );
			
			_CurrentTagSize = tag.length;
			return tag;
		}
		
		private function _GetVideoData () : ByteArray {
			var v:ByteArray = new ByteArray;
			
			// VIDEODATA 'header'
			v.writeByte(0x13); // frametype (1) + codecid (3)
			
			// SCREENVIDEOPACKET 'header'			
			// blockwidth/16-1 (4bits) + imagewidth (12bits)
			_WriteUI4_12(v, int(_BLOCK_WIDTH/16) - 1,  _FrameWidth);
			// blockheight/16-1 (4bits) + imageheight (12bits)
			_WriteUI4_12(v, int(_BLOCK_HEIGHT/16) - 1, _FrameHeight);			

			// VIDEODATA > SCREENVIDEOPACKET > IMAGEBLOCKS:

			var yMax:int = int(_FrameHeight/_BLOCK_HEIGHT);
			var yRemainder:int = _FrameHeight % _BLOCK_HEIGHT; 
			if (yRemainder > 0) yMax += 1;

			var xMax:int = int(_FrameWidth/_BLOCK_WIDTH);
			var xRemainder:int = _FrameWidth % _BLOCK_WIDTH;				
			if (xRemainder > 0) xMax += 1;
				
			for (var y1:int = 0; y1 < yMax; y1++)
			{
				for (var x1:int = 0; x1 < xMax; x1++) 
				{
					// create block
					var block:ByteArray = new ByteArray();
					
					var yLimit:int = _BLOCK_HEIGHT;	
					if (yRemainder > 0 && y1 + 1 == yMax) yLimit = yRemainder;

					for (var y2:int = 0; y2 < yLimit; y2++) 
					{
						var xLimit:int = _BLOCK_WIDTH;
						if (xRemainder > 0 && x1 + 1 == xMax) xLimit = xRemainder;
						
						for (var x2:int = 0; x2 < xLimit; x2++) 
						{
							var px:int = (x1 * _BLOCK_WIDTH) + x2;
							var py:int = _FrameHeight - ((y1 * _BLOCK_HEIGHT) + y2); // (flv's save from bottom to top)
							var p:uint = _Bmp.getPixel(px, py);

							block.writeByte( p & 0xff ); 		// blue	
							block.writeByte( p >> 8 & 0xff ); 	// green
							block.writeByte( p >> 16 ); 		// red
						}
					}
					block.compress();

					_WriteUI16(v, block.length); // write block length (UI16)
					v.writeBytes( block ); // write block
				}
			}
			return v;
		}

		private function _GetFlvTagOnMetaData():ByteArray {
			
			var tag:ByteArray = new ByteArray();
			var dat:ByteArray = _GetMetaData();

			// tag 'header'
			tag.writeByte( 18 ); 					// tagType = script data
			_WriteUI24(tag, dat.length); 			// data size
			_WriteUI24(tag, 0);						// timestamp should be 0 for onMetaData tag
			tag.writeByte(0);						// timestamp extended
			_WriteUI24(tag, 0);						// streamID always 0
			
			// data tag		
			tag.writeBytes( dat );
			
			_CurrentTagSize = tag.length;
			return tag;
		}

		private function _GetMetaData() : ByteArray	{
			// onMetaData info goes in a ScriptDataObject of data type 'ECMA Array'
			var b:ByteArray = new ByteArray();
			
			// ObjectNameType (always 2)
			b.writeByte(2);	
		
			// ObjectName (type SCRIPTDATASTRING):
			_WriteUI16(b, "onMetaData".length); // StringLength
			b.writeUTFBytes( "onMetaData" ); // StringData
		
			// ObjectData (type SCRIPTDATAVALUE):
			
			b.writeByte(8); // Type (ECMA array = 8)
			b.writeUnsignedInt(7) // // Elements in array
		
			// SCRIPTDATAVARIABLES...
			if (_Duration > 0) {
				_WriteUI16(b, "duration".length);
				b.writeUTFBytes("duration");
				b.writeByte(0); 
				b.writeDouble(_Duration);
			}
			
			_WriteUI16(b, "width".length);
			b.writeUTFBytes("width");
			b.writeByte(0); 
			b.writeDouble(_FrameWidth);

			_WriteUI16(b, "height".length);
			b.writeUTFBytes("height");
			b.writeByte(0); 
			b.writeDouble(_FrameHeight);

			_WriteUI16(b, "framerate".length);
			b.writeUTFBytes("framerate");
			b.writeByte(0); 
			b.writeDouble(_FrameRate);

			_WriteUI16(b, "videocodecid".length);
			b.writeUTFBytes("videocodecid");
			b.writeByte(0); 
			b.writeDouble(3); // 'Screen Video' = 3

			_WriteUI16(b, "canSeekToEnd".length);
			b.writeUTFBytes("canSeekToEnd");
			b.writeByte(1); 
			b.writeByte(int(true));

			var mdc:String = "Fleaxy FlvManager";			
			_WriteUI16(b, "metadatacreator".length);
			b.writeUTFBytes("metadatacreator");
			b.writeByte(2); 
			_WriteUI16(b, mdc.length);
			b.writeUTFBytes(mdc);
			
			// VariableEndMarker1 (type UI24 - always 9)
			_WriteUI24(b, 9);
		
			return b;			
		}

		private function _WriteUI24(stream:*, p:uint):void
		{
			var byte1:int = p >> 16;
			var byte2:int = p >> 8 & 0xff;
			var byte3:int = p & 0xff;
			stream.writeByte(byte1);
			stream.writeByte(byte2);
			stream.writeByte(byte3);
		}
		
		private function _WriteUI16(stream:*, p:uint) : void {
			stream.writeByte( p >> 8 )
			stream.writeByte( p & 0xff );
		}

		private function _WriteUI4_12(stream:*, p1:uint, p2:uint) : void {
			// writes a 4-bit value followed by a 12-bit value in two sequential bytes

			var byte1a:int = p1 << 4;
			var byte1b:int = p2 >> 8;
			var byte1:int = byte1a + byte1b;
			var byte2:int = p2 & 0xff;

			stream.writeByte(byte1);
			stream.writeByte(byte2);
		}
	}
}
